package de.hsb.ai.agent.jcau;

import game.Link;
import game.Move;
import game.Node;
import game.Transport;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.TreeSet;

import de.hsb.ai.agent.AbstractDetective;
import de.hsb.ai.domain.MoveEvent;
import de.hsb.ai.util.map.ScotlandJadeMapHelper.ScotlandJadeVertex;
import de.hsb.ai.util.map.ScotlandJadeMapHelper.ScotlandJadeEdge;
/**
 * EstimatingDetectiveTac1 Estimates the possible Positions of Mr. X and moves
 * in direction of position, which is the farthest.
 */
@SuppressWarnings("serial")
public class EstimatingDetective extends AbstractDetective {

	protected Collection<ScotlandJadeVertex> possibleNodes = new HashSet<ScotlandJadeVertex>();
	
	protected int possibleNode = -1;
	
//	protected int debugStartPos = 91;
	protected static int RANGE = 10;
	
	protected int debugEstimatedPosition = 0;
	
	protected int currentPosition = -1;
	
	protected Move nextMove = null;
		
	protected Move nextMoveTmp = null;
	
	protected int searchDepth = Integer.MAX_VALUE;
	
	protected int distanceToFugitive = 0;
	
	
	protected int[] availableTicketsForSecondWay;
	
	Collection <Integer> visitedNodes = new HashSet<Integer>();;
	
	public EstimatingDetective(String serviceNamePrefix) {
		super(serviceNamePrefix);
	}
	
	protected Random random = new Random(42L);

	@Override
	protected Move getMove(List<Move> options) {
		Move m = null;
//		Collection<ScotlandJadeVertex> debugPossibleNodes = possibleNodes;
//		int debugEstimated = debugEstimatedPosition;
		
		//if the A* algorithm doesn't find a appropriate move, "nextMove" is set to null
		//consequently it takes a random move of the available options
		if(nextMove != null){
			for(Move move : options){
				if(move.getNode() == nextMove.getNode() 
						&& move.getType() == nextMove.getType()) m = nextMove;
			}
		}
		//Debug Test
		if(nextMove != null && m == null){
			System.out.println("Dubug");
		}
		
		
		if(m == null){
			m = options.get(Math.abs(random.nextInt()) % options.size());
		}
		
		
		if (m.getNode() != currentPosition){
			currentPosition = m.getNode();
			logger.debug(Integer.toString(currentPosition));
		}
		
		return m;
	}
	
	
	@Override
	protected void handleMove(MoveEvent m) {
		
		super.handleMove(m);
		
		// Player index == 0 indicates a fugitive move
		if (m.getPlayerIndex() == 0) {
			
			//Mr.X appeared?
			if (m.getMove().getNode() != 0) {
				// Store last known fugitive position				
				possibleNodes.clear();
				possibleNodes.add(map.get(m.getMove().getNode()));
			}
			
			if (lastKnownFugitivePosition != -1){							
				
				//only estimate positions, if they are not known currently
				if(m.getMove().getNode() == 0){
//					if(m.getMove().getType() == Transport.BLACK){
//						System.out.println("BLACK");
//					}
					estimatePositions(lastKnownFugitiveTransports.get(lastKnownFugitiveTransports.size() - 1));
				}
			}
		}
	}	
	
	/**
	 * Estimates the positions of Mr. X after he appeared the first time
	 * @param type last Transport Mr. X used
	 */
	protected void estimatePositions(int type){

		Collection<ScotlandJadeVertex> possibleNodesTmp = new HashSet<ScotlandJadeVertex>();
		
		for(ScotlandJadeVertex v : possibleNodes){
			
			for (ScotlandJadeEdge e : map.edgesOf(v)){
				if (e.getType() != type && type != Transport.BLACK)continue;
				ScotlandJadeVertex vertex = null;
				if (map.getEdgeTarget(e).equals(v)) vertex = map.getEdgeSource(e);
				else vertex = map.getEdgeTarget(e);
				possibleNodesTmp.add(vertex);	
			}
			

		}
		possibleNodes.clear();
		possibleNodes = possibleNodesTmp;
		
	}
	
	
	/**
	 * Finds the best path by using the A* Algorithm with heuristics
	 * @param from current positions - change during recursion
	 * @param depth to search into - change during recursion
	 */
	protected void determineNextMove(int from, int depth){
		 
		//Skip if already a better way was found
		if(depth >= searchDepth)return;
		
		//possible target found?
		if(from == possibleNode){
			if (depth == 0) return;
			nextMove = nextMoveTmp;
			searchDepth = depth;
			distanceToFugitive = depth;
			return;
		}
		
		 ScotlandJadeVertex start = map.get(from);
		 for (ScotlandJadeEdge e : map.edgesOf(start)){
			 //Do I have a appropriate ticket for the first Move?
			 if(depth == 0){
			 	int t = getTickets(e.getType());
			 	if(getTickets(e.getType()) < 1)continue;
			 	//create node list to store visited nodes avoiding duplicates
				 visitedNodes = new HashSet<Integer>();
			 }
			 else if(depth == 1){
				 if(availableTicketsForSecondWay[e.getType()] < 1)continue;
			 }
			 
			 //Get neighbour
			 ScotlandJadeVertex vertex = null;
			 if (map.getEdgeTarget(e).equals(start)) vertex = map.getEdgeSource(e);
			 else vertex = map.getEdgeTarget(e);
			 
			 //Skip if there' s another detektive on nextMoveField field
			 
			 if(depth == 0){
				 boolean vertexTaken = false;
				 for(ScotlandJadeVertex position : positions){
					 if(position.getType() == ScotlandJadeVertex.Type.DETECTIVE && vertex.getIndex() == position.getIndex()){
						 vertexTaken = true;
						 break;
					 }
				 }
				 if(vertexTaken)continue;
			 }
			 
			 //Only choose Nodes, which are between (target position + Class.Range)
			 //and (source - Class.Range)
			 //This heuristic is used to improve the algorithm
			 int max = 0;
			 int min = 0;
			 if(from > possibleNode){
				 max = from;
				 min = possibleNode;
			 }
			 else{
				 max = possibleNode;
				 min = from;
			 }
			 min -= RANGE;
			 max += RANGE;
			 if(max >= vertex.getIndex() && vertex.getIndex() >= min){
				 
				 //avoid duplicates by skipping if node was already visited
				 if(visitedNodes.contains(new Integer(vertex.getIndex())))continue;
				 
				 // Store first move in temporary variable
				 // If a better way is found, it will be overwritten
				 if(depth == 0){
					 nextMoveTmp = new Move(vertex.getIndex(), e.getType());
		
				 }
				 visitedNodes.add(new Integer(from));
				 int d = depth + 1;
				 
				 
				 //Simulate taken ticket for depth 1
				 if (depth == 0){
					 availableTicketsForSecondWay = new int[4];
					 availableTicketsForSecondWay = tickets.clone();
					 --availableTicketsForSecondWay[e.getType()]; 
				 }
				 
				 
				 //Recursive function call with modified parameters
				 determineNextMove(vertex.getIndex(), d);
			 }
			 else continue;		 
			
		}	 
	}
}
